/*
 * server.c
 *
 *  Created on: 2012-01-06
 *      Author: mateusz
 */
#include <stdio.h>
#include <stdlib.h>
#include "shared.h"
#include "server.h"
#include <sys/msg.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <signal.h>

int publicQueueID, shmSem, shmid, gameRunning, serverRunning = 1, clientID;
struct clientProcessInfo clientsProcesses[CLIENTS_COUNT];
int freeSlots[CLIENTS_COUNT];

int main() {
	int clientsCounter = 0, message, msgType, processPID, id, zombieProcesses;

	// create new queue
	publicQueueID = getQueue(SERVER_QUEUE, 0600 | IPC_CREAT, 1);

	signal(SIGINT, stopServer);

	while (serverRunning) {
		gameRunning = 1;
		zombieProcesses = 0;

		while (clientsCounter < CLIENTS_COUNT && gameRunning) {
			// wait for msg
			message = getIntMsg(publicQueueID, 0);
			msgType = intMsg.type;
			if (msgType == CS_INIT) {
				printf("Odebrałem klienta o pid %d\n", message);
				// make process for this client
				// message is pid of client
				id = findFreeSlot();
				clientsProcesses[clientsCounter].id = id;

				processPID = newClient(message, id);

				clientsProcesses[clientsCounter].clientPID = message;
				clientsProcesses[clientsCounter].clientProcessPID = processPID;
				freeSlots[id] = message;

				// wait for ready message from client process
				getIntMsg(publicQueueID, CS_CLIENT_PROCESS_READY);

				clientsCounter += 1;
			} else if (msgType == CS_EXIT) {
				removeClient(message);
				clientsCounter -= 1;
			}
		}
		if (gameRunning == 1) {
			// start game

			startGame();

			while (gameRunning) {
				message = getIntMsg(publicQueueID, 0);
				msgType = intMsg.type;
				if (msgType == CS_EXIT) {
					printf("DOSTAŁEM WIAD ŻE KLIENT %d CHCE WYJŚĆ...\n", message);
					removeClient(message);
					clientsCounter -= 1;
					stopGame();
				} else if (msgType == CS_BATTLE) {
					serverBattle();
				}
				else if(msgType == CS_ATTACK_PROCESS_CLOSED){
					zombieProcesses += 1;
				}
			}
		}
		removeAllClients(zombieProcesses);
		clientsCounter = 0;
	}
	return 0;
}

int findFreeSlot() {
	int i;

	for (i = 0; i < CLIENTS_COUNT; i++) {
		if (freeSlots[i] == 0) {
			return i;
		}
	}
	return -1;
}

void removeAllClients(int zombies) {
	int i, queue, msg, msgType;
	printf("Czekam na zombie..\n");
	for(i = 0;i>zombies;i++){
		wait(NULL);
	}

	printf("Usuwam wszystkich klientów\n");
	for (i = 0; i < CLIENTS_COUNT; i++) {
		if (freeSlots[i] != 0) {
			queue = getQueue(clientsProcesses[i].clientProcessPID, 0600, 0);
			logMsg.logID = LOG_EXIT;
			sendLogMsg(queue, CS_LOG);

			// wait for confirm from client before remove
			msg = getIntMsg(queue, CS_EXIT_CONFIRMED);
			msgType = intMsg.type;
			removeClient(clientsProcesses[i].clientPID);
		}
	}
}

void removeClient(int pid) {
	int i;
	// search client
	for (i = 0; i < CLIENTS_COUNT; i++) {
		if (clientsProcesses[i].clientPID == pid) {
			break;
		}
	}

	if (i < CLIENTS_COUNT) {
		printf("\tUsuwam klienta o pid %d\n", pid);
		kill(clientsProcesses[i].clientProcessPID, SIGALRM);
		wait(NULL);
		// free slot
		freeSlots[clientsProcesses[i].id] = 0;
	}
}

void startGame() {
	int queue, i;

	gameRunning = 1;

	// notify all clients processes
	for (i = 0; i < CLIENTS_COUNT; i++) {
		queue = getQueue(clientsProcesses[i].clientProcessPID, 0600, 0);
		sendIntMsg(queue, CS_SERVER_START, CS_SERVER_START);
	}

}

// clean all client-own shms, queues, semaphores
void cleanEverything(int serverQueue, int csQueue) {
	msgctl(serverQueue, IPC_RMID, NULL);
	msgctl(csQueue, IPC_RMID, NULL);
	semctl(shmSem, 0, IPC_RMID, NULL);
	shmctl(shmid, IPC_RMID, NULL);
}

void stopGame() {
	gameRunning = 0;
}

void stopServer() {
	stopGame();
	serverRunning = 0;
}

int newClient(int clientPID, int id) {
	int clientProcessPID = fork(), pid, csQueue, serverQueue;
	int childs[SUBPROCESSES];

	if (clientProcessPID == 0) {
		clientID = id;

		// add signal action
		signal(SIGALRM, stopGame);
		signal(SIGINT, SIG_DFL);

		// create private client/server-clientProcess queue
		csQueue = getQueue(getpid(), 0600 | IPC_CREAT, 1);

		// notify client
		sendIntMsg(publicQueueID, clientPID, getpid());

		// create shm and sempahore
		//set semaphore on shared memory acces
		shmSem = newSem(getpid(), 1);

		// create shared memory - to do - czyszczenie
		shmid = newShm(getpid(), sizeof(shm));

		// fork to sender
		pid = fork();
		if (pid == 0) {
			sender(csQueue);
		} else {
			childs[0] = pid;

			// create private client process on server queue
			serverQueue = getQueue(pid, 0600 | IPC_CREAT, 1);

			// notify sender that private queue is ready
			sendIntMsg(csQueue, CS_PRIVATE_SERVER_QUEUE_READY, CS_PRIVATE_SERVER_QUEUE_READY);

			// send ready message to server
			sendIntMsg(publicQueueID, CS_CLIENT_PROCESS_READY, CS_CLIENT_PROCESS_READY);

			// wait for start msg
			getIntMsg(csQueue, CS_SERVER_START);

			// if game is still running
			if (gameRunning == 1) {
				sendIntMsg(csQueue, CS_CLIENT_START, CS_CLIENT_START);
				//sendLogMsg(csQueue, CS_LOG, "Hello");
				sendIntMsg(serverQueue, S_LOG_CMD, S_SAY_HELLO);

				// make supplies process
				pid = fork();
				if (pid == 0) {
					supplier(serverQueue);
				} else {
					childs[1] = pid;

					pid = fork();
					if (pid == 0) {
						factory(serverQueue);
					} else {
						childs[2] = pid;

						// make creator process
						pid = fork();
						if (pid == 0) {
							creator(csQueue, serverQueue);
						} else {
							childs[3] = pid;

							receiver(csQueue, serverQueue);

							int i;
							for (i = 0; i < 4; i++) {
								kill(childs[i], SIGINT);
								wait(NULL);
							}

							// clean ipcs
							cleanEverything(serverQueue, csQueue);
						}
					}
				}

			} else {
				// remove sender process
				kill(childs[0], SIGINT);
				wait(NULL);
				// clean ipcs
				cleanEverything(serverQueue, csQueue);
			}
		}

		exit(0);
	}
	return clientProcessPID;
}

void sender(int csQueue) {
	int serverQueue, msg, msgType, clientMsg;

	// get msg from client process that private server queue is ready
	getIntMsg(csQueue, CS_PRIVATE_SERVER_QUEUE_READY);

	// get private client process on server queue
	serverQueue = getQueue(getpid(), 0600, 0);

	while (gameRunning == 1) {
		msg = getIntMsg(serverQueue, (-1) * S_LAST_SENDER_MSG);
		msgType = intMsg.type;
		switch (msgType) {
			case S_LOG_CMD :
				clientMsg = 0;
				switch (msg) {
					case S_ENTITIES_WILL_BE_CREATED :
						clientMsg = LOG_ENTITIES_WILL_BE_CREATED;
						break;
					case S_NOT_ENOUGH_SUPPLIES :
						clientMsg = LOG_NOT_ENOUGH_SUPPLIES_TO_CREATE_ENTITIES;
						break;
					case S_SAY_HELLO :
						clientMsg = LOG_WELCOME_MESSAGE;
						break;
					case S_NOT_ENOUGH_UNITS_TO_MAKE_ATTACK :
						clientMsg = LOG_NOT_ENOUGH_UNITS_TO_MAKE_ATTACK;
						break;
					case S_ATTACK_CMD :
						clientMsg = LOG_MAKING_ATTACK;
						break;
				}

				logMsg.logID = clientMsg;
				sendLogMsg(csQueue, CS_LOG);

				break;
			case S_ENTITIES_UPDATE :
				fillEntities();
				// notify sender
				sendEntitiesMsg(csQueue, CS_STATS);
				break;
			case S_WIN_END_GAME:
				logMsg.logID = LOG_WIN_EXIT;
				sendLogMsg(csQueue, CS_LOG);
		}

	}
}

void receiver(int csQueue, int serverQueue) {
	int commandID, msgType, msg, i;
	while (gameRunning == 1) {
		msg = getCommandMsg(csQueue, CS_COMMAND);
		if (msg != -1) {
			msgType = commandMsg.type;
			commandID = commandMsg.commandId;

			switch (commandID) {
				case COMMAND_CREATE_ENTITIES :
					sendCommandMsg(serverQueue, S_CREATE_ENTITIES_CMD);
					break;
				case COMMAND_ATTACK :
					if (fork() == 0) {
						// make new attack process
						attack(serverQueue, csQueue);
					}
					break;
				case COMMAND_WAS_ATTACK :
					logMsg.logID = LOG_WAS_ATTACK;
					for (i = FIRST_ENTITY; i <= LAST_ENTITY; i++) {
						logMsg.params[i] = commandMsg.params[i];
					}
					sendLogMsg(csQueue, CS_LOG);
					break;

				case COMMAND_WAS_BATTLE :
					logMsg.logID = LOG_WAS_DEFENSE;
					for (i = FIRST_ENTITY; i <= LAST_ENTITY; i++) {
						logMsg.params[i] = commandMsg.params[i];
					}
					sendLogMsg(csQueue, CS_LOG);
					break;
			}
		}
	}
}

void supplier(int serverQueue) {
	struct serverShm *mem = (struct serverShm*) shmat(shmid, NULL, 0);

	entitiesMsg.type = CS_STATS;

	// initialize supplies
	semDown(shmSem);
	mem->units[SUPPLIES] = INIT_SUPPLIES;
	mem->wins = 0;
	semUp(shmSem);
	while (gameRunning == 1) {
		sleep(SUPPLIER_SLEEP);
		// update supplies
		semDown(shmSem);
		mem->units[SUPPLIES] += SUPPLIES_PER_SEC + mem->units[WORKERS] * SUPPLIES_PER_WORKER;
		semUp(shmSem);

		// notify sender
		sendIntMsg(serverQueue, S_ENTITIES_UPDATE, S_ENTITIES_UPDATE);
	}
}

void fillEntities() {
	int i;
	struct serverShm *mem = (struct serverShm*) shmat(shmid, NULL, 0);

	semDown(shmSem);
	for (i = 0; i <= LAST_ENTITY; i++) {
		entitiesMsg.units[i] = mem->units[i];
	}
	entitiesMsg.wins = mem->wins;
	semUp(shmSem);
}

void factory(int serverQueue) {
	int msg, neededUnits, i, currentUnits, j;
	struct serverShm *mem = (struct serverShm*) shmat(shmid, NULL, 0);

	while (gameRunning == 1) {

		msg = getCommandMsg(serverQueue, S_CREATE_ENTITIES_CMD);

		if (msg != -1) {
			// calculate needed units
			neededUnits = 0;

			for (i = 0; i <= LAST_ENTITY; i++) {
				neededUnits += commandMsg.params[i] * entitiesInfo[i].cost;
			}

			currentUnits = mem->units[SUPPLIES];

			if (neededUnits > currentUnits) {
				sendIntMsg(serverQueue, S_LOG_CMD, S_NOT_ENOUGH_SUPPLIES);
			} else {
				semDown(shmSem);
				mem->units[SUPPLIES] -= neededUnits;

				// put entities to the queue
				for (i = FIRST_ENTITY; i <= LAST_ENTITY; i++) {
					createEntityMsg.entity = i;
					createEntityMsg.timeLeft = entitiesInfo[i].createTime;
					for (j = 0; j < commandMsg.params[i]; j++) {
						sendCreateEntityMsg(serverQueue, S_CREATE_ENTITY_CMD);
						mem->creatorListSize += 1;
					}
				}
				semUp(shmSem);
				sendIntMsg(serverQueue, S_LOG_CMD, S_ENTITIES_WILL_BE_CREATED);
			}
		}
	}
}

void creator(int csQueue, int serverQueue) {
	struct serverShm *mem = (struct serverShm*) shmat(shmid, NULL, 0);
	int msg;

	while (gameRunning == 1) {
		msg = getCreateEntityMsg(serverQueue, S_CREATE_ENTITY_CMD);
		if (msg != -1) {
			while (createEntityMsg.timeLeft > 0) {
				sleep(CREATOR_SLEEP);
				createEntityMsg.timeLeft -= CREATOR_SLEEP;
			}
			// time le 0
			semDown(shmSem);
			mem->units[createEntityMsg.entity] += 1;
			mem->creatorListSize -= 1;
			semUp(shmSem);

			// notify sender
			sendIntMsg(serverQueue, S_ENTITIES_UPDATE, S_ENTITIES_UPDATE);
		}
	}
}

void attack(int serverQueue, int csQueue) {
	struct serverShm *mem = (struct serverShm*) shmat(shmid, NULL, 0);
	int i, notEnoughUnits = 0;

	battleMsg.success = 0;
	battleMsg.attackerID = clientID;
	battleMsg.defenderID = (clientID + 1) % 2;

	// copy entities to battle struct and remove temporary from client
	semDown(shmSem);
	// check if there is enough units to make attack
	for (i = FIRST_ENTITY; i <= LAST_ENTITY; i++) {
		if (commandMsg.params[i] > mem->units[i]) {
			notEnoughUnits = 1;
		}
	}
	semUp(shmSem);

	// send msg to server about zombie process
	sendIntMsg(publicQueueID, CS_ATTACK_PROCESS_CLOSED, CS_ATTACK_PROCESS_CLOSED);

	if (notEnoughUnits == 0) {

		semDown(shmSem);
		// copy entities
		for (i = FIRST_ENTITY; i <= LAST_ENTITY; i++) {
			battleMsg.attackerUnits[i] = commandMsg.params[i];
			mem->units[i] -= commandMsg.params[i];
		}
		semUp(shmSem);

		sendIntMsg(serverQueue, S_LOG_CMD, S_ATTACK_CMD);
		sendIntMsg(publicQueueID, CS_BATTLE, CS_BATTLE);
		sendBattleMsg(publicQueueID, S_BATTLE);

		// wait for server response
		getBattleMsg(csQueue, CS_BATTLE_END);

		if (battleMsg.success == 1) {
			semDown(shmSem);
			mem->wins += 1;

			if(mem->wins == WINS_TO_END){
				sendIntMsg(serverQueue, S_WIN_END_GAME, S_WIN_END_GAME);
			}

			semUp(shmSem);
		}

		sleep(ATTACK_SLEEP);

		// send log
		commandMsg.commandId = COMMAND_WAS_ATTACK;
		for (i = FIRST_ATTACKED_ENTITY; i <= LAST_ENTITY; i++) {
			commandMsg.params[i] = battleMsg.attackerUnits[i];
		}
		sendCommandMsg(csQueue, CS_COMMAND);

		// give units back
		semDown(shmSem);
		for (i = FIRST_ENTITY; i <= LAST_ENTITY; i++) {
			mem->units[i] += battleMsg.attackerUnits[i];
		}
		semUp(shmSem);
	}

	else {
		sendIntMsg(serverQueue, S_LOG_CMD, S_NOT_ENOUGH_UNITS_TO_MAKE_ATTACK);
	}
	exit(0);
}

void serverBattle() {
	int shmID, semID, attackerPID, defenderPID, i, attackerQueue, defenderQueue;
	double attackerAttack = 0, attackerDefense = 0, defenderAttack = 0, defenderDefense = 0;
	int defenderUnitsCopy[LAST_ENTITY + 1];

	getBattleMsg(publicQueueID, S_BATTLE);
	attackerPID = clientsProcesses[battleMsg.attackerID].clientProcessPID;
	defenderPID = clientsProcesses[battleMsg.defenderID].clientProcessPID;

	// create shm and sempahore to defender
	//set semaphore on shared memory acces
	semID = getSem(defenderPID);

	// create shared memory
	shmID = getShm(defenderPID, sizeof(shm));

	struct serverShm *mem = (struct serverShm*) shmat(shmID, NULL, 0);

	semDown(semID);
	// defender copy and sums
	for (i = FIRST_ATTACKED_ENTITY; i <= LAST_ENTITY; i++) {
		defenderUnitsCopy[i] = mem->units[i];

		defenderAttack += entitiesInfo[i].atack * mem->units[i];
		defenderDefense += entitiesInfo[i].defense * mem->units[i];

		attackerAttack += entitiesInfo[i].atack * battleMsg.attackerUnits[i];
		attackerDefense += entitiesInfo[i].defense * battleMsg.attackerUnits[i];
	}

	printf("Before battle:\nAttacker: ");
	for (i = FIRST_ATTACKED_ENTITY; i <= LAST_ENTITY; i++) {
		printf("%d: %d; ", i, battleMsg.attackerUnits[i]);
	}

	printf("\nDefender: ");
	for (i = FIRST_ATTACKED_ENTITY; i <= LAST_ENTITY; i++) {
		printf("%d: %d; ", i, mem->units[i]);
	}

	printf("\nTotal: AA: %lf, AD: %lf, DA: %lf, DD: %lf\n", attackerAttack, attackerDefense, defenderAttack,
			defenderDefense);

	// attacker
	if (attackerAttack > defenderDefense || defenderDefense == 0) {
		if (attackerAttack > defenderDefense) {
			printf("Hurra.. punkt!\n");
			battleMsg.success = 1;
		}
		for (i = FIRST_ATTACKED_ENTITY; i <= LAST_ENTITY; i++) {
			mem->units[i] = 0;
		}
	} else {
		// calculate defender losts
		for (i = FIRST_ATTACKED_ENTITY; i <= LAST_ENTITY; i++) {
			mem->units[i] -= (int) mem->units[i] * attackerAttack / defenderDefense;
		}
	}

	// defender
	if (defenderAttack > attackerDefense || attackerDefense == 0) {
		for (i = FIRST_ATTACKED_ENTITY; i <= LAST_ENTITY; i++) {
			battleMsg.attackerUnits[i] = 0;
		}
	} else {
		// calculate attacker losts
		for (i = FIRST_ATTACKED_ENTITY; i <= LAST_ENTITY; i++) {
			battleMsg.attackerUnits[i] -= (int) battleMsg.attackerUnits[i] * defenderAttack / attackerDefense;
		}
	}

	semUp(semID);

	printf("After battle:\nAttacker: ");
	for (i = FIRST_ATTACKED_ENTITY; i <= LAST_ENTITY; i++) {
		printf("%d: %d; ", i, battleMsg.attackerUnits[i]);
	}

	printf("\nDefender: ");
	for (i = FIRST_ATTACKED_ENTITY; i <= LAST_ENTITY; i++) {
		printf("%d: %d; ", i, mem->units[i]);
	}
	printf("\n");

	// send message to attacker
	attackerQueue = getQueue(attackerPID, 0600, 0);
	sendBattleMsg(attackerQueue, CS_BATTLE_END);

	// send message to defender
	defenderQueue = getQueue(defenderPID, 0600, 0);
	commandMsg.commandId = COMMAND_WAS_BATTLE;
	semDown(semID);
	for (i = FIRST_ATTACKED_ENTITY; i <= LAST_ENTITY; i++) {
		commandMsg.params[i] = defenderUnitsCopy[i] - mem->units[i];
	}
	semUp(semID);
	sendCommandMsg(defenderQueue, CS_COMMAND);
}

int sendBattleMsg(int qID, long type) {
	battleMsg.type = type;
	return msgsnd(qID, &battleMsg, sizeof(battleMsg) - sizeof(long), 0);
}
int getBattleMsg(int qID, long type) {
	return msgrcv(qID, &battleMsg, sizeof(battleMsg) - sizeof(long), type, 0);
}

int sendCreateEntityMsg(int qID, long type) {
	createEntityMsg.type = type;
	return msgsnd(qID, &createEntityMsg, sizeof(createEntityMsg) - sizeof(long), 0);
}

int getCreateEntityMsg(int qID, long type) {
	return msgrcv(qID, &createEntityMsg, sizeof(createEntityMsg) - sizeof(long), type, 0);
}
